目标

  • 使用基于Haar特征的级联分类器进行人脸检测
  • 把它扩展到眼睛检测等领域

基础

基于Haar特征的级联分类器进行目标检测是Paul Viola和Michael Jones在2001年发表的论文《Rapid Object Detection using a Boosted Cascade of Simple Features》中提出的一种有效的目标检测方法。它基于机器学习的方法,由大量的正负样本图像训练级联函数,然后用于检测其他图像中的对象。

现在我们将研究人脸检测。该算法首先需要大量的正图像(人脸图像)和负图像(无人脸图像)来训练分类器。然后我们需要从中提取特征。使用如下图所示的Haar特性。它们就像卷积核一样。每个特征都是一个值,这个值等于黑色矩形中的像素值之后减去白色矩形中的像素值之和。 image1 使用所有可能的核来计算足够多的特征。(想象一下这需要多少计算量?仅仅是一个24x24的窗口就有160000个特征)。对于每一个特征的计算我们好需要计算白色和黑色矩形内的像素和。为了解决这个问题,作者引入了积分图像,这可以大大的简化求和运算,对于任何一个区域的像素和只需要对积分图像上的四个像素操作即可,它可以使运算速度飞快!
但是在我们计算得到的所有的这些特征中,大多数是不相关的。如下图所示,上边一行显示了两个好的特征,第一个特征看上去是对眼部周围区域的描述,因为眼睛总是比鼻子黑一些。第二个特征是描述的是眼睛比鼻梁要黑一些。但是如果把这两个窗口放到脸颊的话,就一点都不相关。那么我们怎样从超过160000+个特征中选出最好的特征呢?使用 Adaboost。
image2

为此,我们将每一个特征应用于所有的训练图像。对于每一个特征,我们要找到它能够区分出正样本和负样本的最佳阈值。显然,这会出现错误或错误分类。我们要选取错误率最低的特征,这意味着它们是最准确地对面部和非面部图像进行分类的特征。(这个过程其实不像我们说的这么简单。在开始时每张图像都具有相同的权重,每次分类之后,被错分的图像的权重会增大,然后进行相同的处理,我们又得到新的错误率和新的权重。重复执行这个过程,直到达到要求的准确率或者错误率或者要求数目的特征)。

最终的分类器是这些弱分类器的加权和。之所以被称为弱分类,是因为它不能单独对图像进行分类,而是与其他分类器一起构成一个强分类器。文章中说200个特征就能够提供95%的准确度了。他们最后使用6000个特征。(从160000减到6000,效果显著)。

现在你有一幅图像,对每一个 24x24 的窗口使用这 6000 个特征来做检查,看它是不是面部。这很低效很耗时,但作者有更好的解决方法。

在一副图像中大多数区域是非面部区域。所以最好有一个简单的方法来证明这个窗口不是面部区域,如果不是就直接抛弃,不用对它再做处理。相反,把注意力集中在可能有人脸的区域。按照这种方法我们可以在可能是面部的区域多花点时间。

为了达到这个目的,作者提出了级联分类器的概念。不是在一开始就对窗口应用这 6000 个特征,而是将这些特征分成不同组,在不同的分类阶段逐个使用。(通常前面很少的几个阶段使用较少的特征检测)。如果一个窗口第一阶段的检测都过不了就可以直接放弃后面的测试了,如果它通过了就进入第二阶段的检测。如果一个窗口经过了所有的测试,那么这个窗口就被认为是面部区域。

作者将 6000 多个特征分为 38 个阶段,前五个阶段的特征数分别为 1,10,25,25 和 50。(上图中的两个特征其实就是从 Adaboost 获得的最好特征)。根据作者的说法,平均每个子窗口可以评估6000多个功能中的10个。

上面是我们对 Viola-Jones 人脸检测工作原理的简单直观的解释。阅读这篇文章以获得更多细节,或者查看附加参考资料部分中的参考资料。

OpenCV 中的 Haar 级联检测

OpenCV 自带了训练器和检测器。如果你想自己训练一个分类器来检测汽车,飞机等的话,可以使用 OpenCV 构建。其中的细节在这里:Cascade Classifier Training
现在我们讨论如何使用检测器。OpenCV 已经包含了很多已经训练好的分类器,其中包括:面部,眼睛,微笑等。这些 XML 文件保存在/opencv/data/haarcascades/文件夹中。下面我们将使用 OpenCV 创建一个面部和眼部检测器。
首先我们要加载需要的 XML 分类器。然后以灰度格式加载输入图像或者是视频。

import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

img = cv2.imread('sachin.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

现在我们在图像中检测面部。如果检测到面部,它会返回面部所在的矩形
区域 Rect(x,y,w,h)。一旦我们获得这个位置,我们可以创建一个 ROI 并在
其中进行眼部检测。(谁让眼睛长在脸上呢)

faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
    roi_gray = gray[y:y+h, x:x+w]
    roi_color = img[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(roi_gray)
    for (ex,ey,ew,eh) in eyes:
        cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果如下: image3